﻿/*
 * 	Copyright 2012 Lookstone Technologies
 *	Written by Jason Seeley
 * 
 *	This file and other files in Lookstone.Common may be freely used, 
 *	modified, and distributed so long as this block remains intact 
 *  in each file it appears in.
 * 
 *	Lookstone Technologies does not warrant that any of this source code 
 *	is free from defect, and will not accept responsibility for any 
 *  failure of software using this library.  By using this library, 
 *  you agree to that Lookstone Technologies and the author of this file 
 *	are not liable for any damages or injuries caused through the use
 *	of this library.
 */

using System;
using System.Collections;
using System.Data;
using System.Data.SQLite;

namespace Lookstone.Data.SQLite
{
	/// <summary>
	/// Summary description for SqlConnectionContext.
	/// </summary>
	public class SQLiteConnectionContext : IConnectionContext, IDisposable
	{
		private SQLiteConnection _connection = null;
		private string _connectionString = null;
		private SQLiteTransaction _transaction = null;
		private int _transactCount = 0;
		private bool _rollbackStarted = false;

		/// <summary>
		/// Constructs a SqlConnectionContext object
		/// </summary>
		public SQLiteConnectionContext()
		{
		}

		/// <summary>
		/// Constructs a SqlConnectionContext object
		/// </summary>
		/// <param name="connectionString">Connection string for the connection</param>
		public SQLiteConnectionContext(string connectionString, bool open = true)
		{
			_connectionString = connectionString;
			if (open)
				Connect();
		}

		#region IDisposable Members
		public void Dispose()
		{
			Close();
			GC.SuppressFinalize(this);
		}
		#endregion

		/// <summary>
		/// Gets or sets the connection string.  Will not affect an open connection.
		/// </summary>
		public string ConnectionString
		{
			get { return _connectionString; }
			set { _connectionString = value; }
		}

		/// <summary>
		/// Opens a connection to the database
		/// </summary>
		public void Connect()
		{
			if (_connection != null)
				throw new DataException("ConnectionContext already open!");

			_connection = new SQLiteConnection(_connectionString);
			try
			{
				_connection.Open();
			}
			catch (Exception ex)
			{
				System.Diagnostics.Trace.WriteLine(ex.Message);
				throw new DataException("Could not open data source", ex);
			}
		}

		/// <summary>
		/// Opens a connection to the database from the passed in connection string
		/// </summary>
		/// <param name="connectionString">A valid connection string to open the database connection</param>
		public void Connect(string connectionString)
		{
			_connectionString = connectionString;
			Connect();
		}

		/// <summary>
		/// Closes the SqlConnectionContext (and rolls back any active transactions)
		/// </summary>
		public void Close()
		{
			if (_transaction != null)
			{
				// Automatically rollback the transaction, because it should not be committed if the connection is being closed
				_transaction.Rollback();
				_transaction.Dispose();
				_transaction = null;
			}

			if (_connection != null)
			{
				_connection.Dispose();
				_connection = null;
			}
		}

		/// <summary>
		/// Connects an SQLiteCommand object to the connection (and to the transaction, if applicable)
		/// </summary>
		/// <param name="cmd">SQLiteCommand to connect</param>
		public void ConnectCommand(SQLiteCommand cmd)
		{
			if (!IsConnected)
				Connect();

			cmd.Connection = _connection;
			cmd.Transaction = _transaction;
		}

		void IConnectionContext.ConnectCommand(IDbCommand cmd)
		{
			ConnectCommand((SQLiteCommand)cmd);
		}

		/// <summary>
		/// Gets the Open state of the SqlConnectionContext
		/// </summary>
		public bool IsConnected
		{
			get
			{
				if (_connection == null || !_connection.State.HasFlag(ConnectionState.Open))
					return false;

				return true;
			}
		}

		/// <summary>
		/// Gets the Transaction status of the SqlConnectionContext
		/// </summary>
		public bool IsTransacting
		{
			get { return (_transaction != null); }
		}

		/// <summary>
		/// Begins a transaction
		/// </summary>
		public void BeginTransaction()
		{
			// Begin the transaction if there isn't a transaction
			// already active (avoid nested transactions)
			if (_transaction == null)
			{
				_transaction = _connection.BeginTransaction();
				_rollbackStarted = false;
			}

			// Increment the transaction count
			_transactCount++;
		}

		/// <summary>
		/// Commits a transaction
		/// </summary>
		public void CommitTransaction()
		{
			if (_rollbackStarted)
				throw new DataException("Cannot commit a transaction once a rollback has begun");
			else if (_transaction == null)
				throw new DataException("No transaction to commit!");

			// Commit the transaction when the nested transaction count reaches 0
			if (--_transactCount == 0)
			{
				_transaction.Commit();
				_transaction.Dispose();
				_transaction = null;
				_rollbackStarted = false;
			}
		}

		/// <summary>
		/// Rolls back a transaction in progress
		/// </summary>
		public void RollbackTransaction()
		{
			_rollbackStarted = true;

			if (--_transactCount == 0)
			{
				_transaction.Rollback();
				_transaction.Dispose();
				_transaction = null;
				_rollbackStarted = false;
			}
		}

		/// <summary>
		/// Rolls back a transaction in progress, and throws an exception
		/// </summary>
		/// <param name="inner">InnerException to be re-thrown</param>
		public void RollbackTransaction(Exception inner)
		{
			RollbackTransaction();
			throw new Exception("Rollback of transaction", inner);
		}
	}
}
